/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.cadmiumxml;

import java.io.File;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.validation.Schema;

import fr.x9c.cadmium.kernel.Channel;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.FalseExit;
import fr.x9c.cadmium.kernel.Fatal;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;
import fr.x9c.cadmium.primitives.cadmium.Cadmium;

/**
 * This class provides the primitives for SAX parsers manipulation.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
@PrimitiveProvider
public final class SAX {

    /**
     * No instance of this class.
     */
    private SAX() {
    } // end empty constructor

    /**
     * Constructs a SAX parser factory.
     * @param ctxt context
     * @param unit ignored
     * @return a new SAX parser factory
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_make(final CodeRunner ctxt,
                                                   final Value unit)
        throws Fail.Exception {
        try {
            return Cadmium.createObject(SAXParserFactory.newInstance());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_make(CodeRunner, Value)'

    /**
     * Sets the value of a feature flag.
     * @param ctxt context
     * @param fact factory
     * @param name feature name
     * @param value feature value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_set_feature(final CodeRunner ctxt,
                                                          final Value fact,
                                                          final Value name,
                                                          final Value value)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            spf.setFeature(name.asBlock().asString(), value == Value.TRUE);
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_set_feature(CodeRunner, Value, Value, Value)'

    /**
     * Gets the value of a feature flag.
     * @param ctxt context
     * @param fact factory
     * @param name feature name
     * @return value of passed feature
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_get_feature(final CodeRunner ctxt,
                                                          final Value fact,
                                                          final Value name)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            return spf.getFeature(name.asBlock().asString())
                ? Value.TRUE
                : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_get_feature(CodeRunner, Value, Value)'

    /**
     * Sets the schema for constructed SAX parsers.
     * @param ctxt context
     * @param fact factory
     * @param schema schema to use
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_set_schema(final CodeRunner ctxt,
                                                         final Value fact,
                                                         final Value schema)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            final Schema s = (Schema) schema.asBlock().asCustom();
            spf.setSchema(s);
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_set_schema(CodeRunner, Value, Value)'

    /**
     * Gets the schema for constructed SAX parsers.
     * @param ctxt context
     * @param fact factory
     * @return schema used by constructed parsers
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_get_schema(final CodeRunner ctxt,
                                                         final Value fact)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            return Cadmium.createObject(spf.getSchema());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_get_schema(CodeRunner, Value)'

    /**
     * Sets the 'namespace-aware' property for constructed SAX parsers.
     * @param ctxt context
     * @param fact factory
     * @param value value for 'namespace-aware' property
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_set_namespace_aware(final CodeRunner ctxt,
                                                                  final Value fact,
                                                                  final Value value)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            spf.setNamespaceAware(value == Value.TRUE);
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_set_namespace_aware(CodeRunner, Value, Value)'

    /**
     * Gets the 'namespace-aware' property for constructed SAX parsers.
     * @param ctxt context
     * @param fact factory
     * @return value for 'namespace-aware' property
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_is_namespace_aware(final CodeRunner ctxt,
                                                                 final Value fact)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            return spf.isNamespaceAware() ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_is_namespace_aware(CodeRunner, Value)'

    /**
     * Sets the 'validating' property for constructed SAX parsers.
     * @param ctxt context
     * @param fact factory
     * @param value value for 'validating' property
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_set_validating(final CodeRunner ctxt,
                                                             final Value fact,
                                                             final Value value)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            spf.setValidating(value == Value.TRUE);
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_set_validating(CodeRunner, Value, Value)'

    /**
     * Gets the 'validating' property for constructed SAX parsers.
     * @param ctxt context
     * @param fact factory
     * @return value for 'validating' property
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_is_validating(final CodeRunner ctxt,
                                                            final Value fact)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            return spf.isValidating() ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_is_validating(CodeRunner, Value)'

    /**
     * Sets the 'xinclude-aware' property for constructed SAX parsers.
     * @param ctxt context
     * @param fact factory
     * @param value value for 'xinclude-aware' property
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_set_xinclude_aware(final CodeRunner ctxt,
                                                                 final Value fact,
                                                                 final Value value)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            spf.setXIncludeAware(value == Value.TRUE);
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_set_xinclude_aware(CodeRunner, Value, Value)'

    /**
     * Gets the 'xinclude-aware' property for constructed SAX parsers.
     * @param ctxt context
     * @param fact factory
     * @return value for 'xinclude-aware' property
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_saxfactory_is_xinclude_aware(final CodeRunner ctxt,
                                                                final Value fact)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            return spf.isXIncludeAware() ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_saxfactory_is_xinclude_aware(CodeRunner, Value)'

    /**
     * Constructs a SAX parser.
     * @param ctxt context
     * @param fact factory
     * @return a new SAX parser
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_make(final CodeRunner ctxt,
                                            final Value fact)
        throws Fail.Exception {
        try {
            final SAXParserFactory spf = (SAXParserFactory) fact.asBlock().asCustom();
            return Cadmium.createObject(spf.newSAXParser());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_make(CodeRunner, Value)'

    /**
     * Sets the value of a property.
     * @param ctxt context
     * @param parser parser
     * @param name property name
     * @param obj property value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_set_property(final CodeRunner ctxt,
                                                    final Value parser,
                                                    final Value name,
                                                    final Value obj)
        throws Fail.Exception {
        try {
            final SAXParser sp = (SAXParser) parser.asBlock().asCustom();
            sp.setProperty(name.asBlock().asString(), obj.asBlock().asCustom());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_set_property(CodeRunner, Value, Value, Value)'

    /**
     * Gets the value of a property.
     * @param ctxt context
     * @param parser parser
     * @param name property name
     * @return value of passed property
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_get_property(final CodeRunner ctxt,
                                                    final Value parser,
                                                    final Value name)
        throws Fail.Exception {
        try {
            final SAXParser sp = (SAXParser) parser.asBlock().asCustom();
            return Cadmium.createObject(sp.getProperty(name.asBlock().asString()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_get_property(CodeRunner, Value, Value)'

    /**
     * Gets the schema used by a parser.
     * @param ctxt context
     * @param parser parser
     * @return schema used by passed parser
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_get_schema(final CodeRunner ctxt,
                                                  final Value parser)
        throws Fail.Exception {
        try {
            final SAXParser sp = (SAXParser) parser.asBlock().asCustom();
            return Cadmium.createObject(sp.getSchema());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_get_schema(CodeRunner, Value)'

    /**
     * Gets the 'namespace-aware' property for passed parser.
     * @param ctxt context
     * @param parser parser
     * @return value for 'namespace-aware' property
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_is_namespace_aware(final CodeRunner ctxt,
                                                          final Value parser)
        throws Fail.Exception {
        try {
            final SAXParser sp = (SAXParser) parser.asBlock().asCustom();
            return sp.isNamespaceAware()
                ? Value.TRUE
                : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_is_namespace_aware(CodeRunner, Value)'

    /**
     * Gets the 'validating' property for passed parser.
     * @param ctxt context
     * @param parser parser
     * @return value for 'validating' property
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_is_validating(final CodeRunner ctxt,
                                                     final Value parser)
        throws Fail.Exception {
        try {
            final SAXParser sp = (SAXParser) parser.asBlock().asCustom();
            return sp.isValidating()
                ? Value.TRUE
                : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_is_validating(CodeRunner, Value)'

    /**
     * Gets the 'xinclude-aware' property for passed parser.
     * @param ctxt context
     * @param parser parser
     * @return value for 'xinclude-aware' property
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_is_xinclude_aware(final CodeRunner ctxt,
                                                         final Value parser)
        throws Fail.Exception {
        try {
            final SAXParser sp = (SAXParser) parser.asBlock().asCustom();
            return sp.isXIncludeAware()
                ? Value.TRUE
                : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_is_xinclude_aware(CodeRunner, Value)'

    /**
     * Parses a file with a SAX parser/handler.
     * @param ctxt context
     * @param parser parser
     * @param handler handler, as collection of Objective Caml functions (callbacks)
     * @param filename name of file to parse
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_parse_file(final CodeRunner ctxt,
                                                  final Value parser,
                                                  final Value handler,
                                                  final Value filename)
        throws Fail.Exception, Fatal.Exception, FalseExit {
        try {
            final SAXParser sp = (SAXParser) parser.asBlock().asCustom();
            sp.parse(ctxt.getContext().getInputStreamForPath(filename),
                     new SAXHandler(ctxt, handler));
            return Value.UNIT;
        } catch (final WrappedException we) {
            final Throwable cause = we.getCause();
            if (cause instanceof Fail.Exception) {
                throw ((Fail.Exception) cause);
            } else if (cause instanceof Fatal.Exception) {
                throw ((Fatal.Exception) cause);
            } else if (cause instanceof FalseExit) {
                throw ((FalseExit) cause);
            } else {
                Fail.failWith(cause.toString());
                return Value.UNIT; // never reached
            } // end if/elsif/else
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_parse_file(CodeRunner, Value, Value, Value)'

    /**
     * Parses a channel with a SAX parser/handler.
     * @param ctxt context
     * @param parser parser
     * @param handler handler, as collection of Objective Caml functions (callbacks)
     * @param channel channel to parse
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_parse_channel(final CodeRunner ctxt,
                                                     final Value parser,
                                                     final Value handler,
                                                     final Value channel)
        throws Fail.Exception, Fatal.Exception, FalseExit {
        try {
            final SAXParser sp = (SAXParser) parser.asBlock().asCustom();
            sp.parse(((Channel) channel.asBlock().asCustom()).asInputStream(),
                     new SAXHandler(ctxt, handler));
            return Value.UNIT;
        } catch (final WrappedException we) {
            final Throwable cause = we.getCause();
            if (cause instanceof Fail.Exception) {
                throw ((Fail.Exception) cause);
            } else if (cause instanceof Fatal.Exception) {
                throw ((Fatal.Exception) cause);
            } else if (cause instanceof FalseExit) {
                throw ((FalseExit) cause);
            } else {
                Fail.failWith(cause.toString());
                return Value.UNIT; // never reached
            } // end if/elsif/else
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_parse_channel(CodeRunner, Value, Value, Value)'

    /**
     * Resets a SAX parser.
     * @param ctxt context
     * @param parser parser to reset
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumxml_sax_reset(final CodeRunner ctxt,
                                             final Value parser)
        throws Fail.Exception {
        try {
            final SAXParser sp = (SAXParser) parser.asBlock().asCustom();
            sp.reset();
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumxml_sax_reset(CodeRunner, Value)'

} // end class 'SAX'
